﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;

namespace library
{
    public static class DelayedWrite
    {
        public static byte[] Get(string filename)
        {
            lock (queue)
            {
                DelayedWriteItem item = queue.FirstOrDefault(x => x.Filename == filename);

                if (item != null)
                    return item.Data;
            }

            return null;
        }

        #region Thread Refresh

        static List<DelayedWriteItem> queue = new List<DelayedWriteItem>();

        static ManualResetEvent delayedWriteEvent = new ManualResetEvent(false);

        internal static void Start()
        {
            Thread thread = new Thread(Refresh);

            thread.Start();
        }

        internal static void Stop()
        {
            delayedWriteEvent.Set();
        }

        static void Refresh()
        {
            while (!Client.Stop)
            {
                lock (queue)
                {
                    var items = queue.Where(x => !x.isLocalData).ToList();
                    
                    foreach (var item in items)
                        Write(item);

                    items = queue.Where(x => x.isLocalData).ToList();

                    foreach (var item in items)
                        Write(item);

                    if(!queue.Any())
                        delayedWriteEvent.Reset();
                }

                delayedWriteEvent.WaitOne();
            }
        }

        static void Write(DelayedWriteItem item)
        {
            try
            {
                if (item.isLocalData)
                {
                    File.WriteAllBytes(item.Filename, item.Data);

                    lock (queue)
                        queue.Remove(item);
                }
                else
                {
                    if (!Directory.Exists(Path.GetDirectoryName(item.Filename)))
                        Directory.CreateDirectory(Path.GetDirectoryName(item.Filename));

                    using (Stream stream = new FileStream(item.Filename, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                    {
                        DelayedWriteItem[] same_file;

                        lock (queue)
                            same_file = queue.Where(x => x.Filename == item.Filename).OrderBy(x => x.Offset).ToArray();

                        while (same_file.Length > 0)
                        {
                            List<DelayedWriteItem> toRemove = new List<DelayedWriteItem>();

                            foreach (DelayedWriteItem writeItem in same_file)
                            {
                                stream.Seek(writeItem.Offset * Parameters.packetSize, 0);

                                stream.Write(writeItem.Data, 0, writeItem.Data.Length);

                                toRemove.Add(writeItem);
                            }

                            lock (queue)
                            {
                                queue.RemoveAll(x => toRemove.Any(y => y == x));

                                same_file =
                                    queue.Where(x => x.Filename == item.Filename).OrderBy(x => x.Offset).ToArray();
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
            
            }
        }

        #endregion

        internal static void Add(string filename, byte[] data, int offset = 1)
        {
            lock (queue)
            {
                queue.Add(new DelayedWriteItem { Filename = filename, Data = data, Offset = offset });

                if (queue.Count() == 1)
                    delayedWriteEvent.Set();
            }
        }

        class DelayedWriteItem
        {
            internal bool isLocalData;

            string filename;

            internal string Filename
            {
                get
                {
                    return filename;
                }
                set
                {
                    filename = value;

                    isLocalData = Path.GetDirectoryName(filename) == Parameters.localPacketsDir;
                }
            }

            internal int Offset = 1;

            internal byte[] Data;
        }
    }
}
